package com.wolfking.spi;

import com.google.common.collect.Maps;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import java.util.Arrays;
import java.util.Map;

/**
 * spi service的动态代理的工厂类
 * 经过隔壁老王处理的JDK动态代理的工厂类
 * spring的加载路径，先调用ApplicationContextAware的setApplicationContext,
 * 再调用InitializingBean的afterPropertiesSet，根据Type解析bean
 * FactoryBean 是注入的时候，调用getObject
 *
 * @author 赵伟伟(wolfking)
 * created on 2019-09-29 13:33
 */
class SpiSelectorProxyFactory implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {

    /*slf4j日志*/
    private final Logger logger = LoggerFactory.getLogger(getClass());
    /*通过构造方法注入设置*/
    private final Class<?> spiInterface;
    /*spring的上下文*/
    private ApplicationContext applicationContext;
    /*spring上下文的bean的映射*/
    private Map<String, Object> beans;
    /*接口上的注解*/
    private final SpiInterface spiInterfaceAnnotation;

    SpiSelectorProxyFactory(Class<?> spiInterface) {
        this.spiInterface = spiInterface;
        spiInterfaceAnnotation = spiInterface.getAnnotation(SpiInterface.class);
    }

    @Override
    @SuppressWarnings("all")
    public Object getObject() throws Exception {
        long start = System.currentTimeMillis();
        for (Object obj : beans.values()) {
            if(obj instanceof SpiBaseInterface) {
                SpiBaseInterface.register((SpiBaseInterface) obj);
            }
        }
        if (spiInterfaceAnnotation.proxy() == ProxyType.JDK) {
            logger.info("SpiSelectorProxyFactory create the JDK Dynamic Proxy class ,{}", spiInterface.getName());
            Object obj = new JdkSpiProxy(spiInterface, beans).getSpiProxy();
            logger.info("{} create JDK Dynamic Proxy class use {} ms", spiInterface.getName(), System.currentTimeMillis() - start);
            return obj;
        } else if (spiInterfaceAnnotation.proxy() == ProxyType.JAVASSIST) {
            logger.info("SpiSelectorProxyFactory create the JAVASSIST Dynamic Proxy class ,{}", spiInterface.getName());
            Object obj = AbstractJavassistSpiProxy.getSpiProxy(spiInterface, beans);
            logger.info("{} create javassit Dynamic Proxy class use {} ms", spiInterface.getName(), System.currentTimeMillis() - start);
            return obj;
        }
        return null;
    }

    @Override
    public Class<?> getObjectType() {
        return spiInterface;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    @Override
    public void afterPropertiesSet() {
        beans = Maps.newHashMap();
        logger.info("current spi service is {}", spiInterface.getName());
        applicationContext.getBeansOfType(spiInterface).values().forEach(bean -> {
            Class<?> clazz = assemblySpiClass(bean);
            if (clazz != null) {
                SpiInterface spiInterface = clazz.getAnnotation(SpiInterface.class);
                if (spiInterface != null) {
                    Arrays.stream(spiInterface.code()).forEach(code -> beans.put(code, bean));
                    beans.put(clazz.getName(), bean);
                    logger.info("mapping code is {}, implement is {}", Arrays.toString(spiInterface.code()), clazz.getName());
                }
            }else {
                beans.put(bean.getClass().getName(), bean);
            }
        });
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    /**
     * 获取spi的注解
     *
     * @param obj spring加载的bean
     * @return spi的注解实例
     */
    private Class<?> assemblySpiClass(Object obj) {
        Class<?> clazz = obj.getClass();
        do {
            SpiInterface spiInterface = clazz.getAnnotation(SpiInterface.class);
            if (spiInterface != null) {
                return clazz;
            }
            clazz = clazz.getSuperclass();
        } while (clazz != null && !clazz.equals(Object.class));
        return null;
    }
}
